/**
 * elancom Corporation copy right 2012 - 2015  
 *   
 * $Header: https://192.168.1.224/svn/elancom/product/elancom-platform/trunk/src/main/java/com/elancom/framework/commons/json/util/DeserializeBeanInfo.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Id: DeserializeBeanInfo.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Author: lanzhouxue $
 * $Date: 2012-07-02 11:09:12 +0800 (Mon, 02 Jul 2012) $
 * $Revision: 1034 $
 */
package com.elancom.framework.commons.json.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import com.elancom.framework.commons.json.JSONException;
import com.elancom.framework.commons.json.annotation.JSONCreator;
import com.elancom.framework.commons.json.annotation.JSONField;

public class DeserializeBeanInfo {

	private final Class<?> clazz;
	private final Type type;
	private Constructor<?> defaultConstructor;
	private Constructor<?> creatorConstructor;
	private Method factoryMethod;

	private final List<FieldInfo> fieldList = new ArrayList<FieldInfo>();

	public DeserializeBeanInfo(Class<?> clazz) {
		super();
		this.clazz = clazz;
		type = clazz;
	}

	public Constructor<?> getDefaultConstructor() {
		return defaultConstructor;
	}

	public void setDefaultConstructor(Constructor<?> defaultConstructor) {
		this.defaultConstructor = defaultConstructor;
	}

	public Constructor<?> getCreatorConstructor() {
		return creatorConstructor;
	}

	public void setCreatorConstructor(Constructor<?> createConstructor) {
		creatorConstructor = createConstructor;
	}

	public Method getFactoryMethod() {
		return factoryMethod;
	}

	public void setFactoryMethod(Method factoryMethod) {
		this.factoryMethod = factoryMethod;
	}

	public Class<?> getClazz() {
		return clazz;
	}

	public Type getType() {
		return type;
	}

	public List<FieldInfo> getFieldList() {
		return fieldList;
	}

	public boolean add(FieldInfo field) {
		for (final FieldInfo item : fieldList) {
			if (item.getName().equals(field.getName())) {
				return false;
			}
		}
		fieldList.add(field);

		return true;
	}

	public static DeserializeBeanInfo computeSetters(Class<?> clazz) {
		final DeserializeBeanInfo beanInfo = new DeserializeBeanInfo(clazz);

		final Constructor<?> defaultConstructor = getDefaultConstructor(clazz);
		if (defaultConstructor != null) {
			defaultConstructor.setAccessible(true);
			beanInfo.setDefaultConstructor(defaultConstructor);
		} else if (defaultConstructor == null && !(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()))) {
			final Constructor<?> creatorConstructor = getCreatorConstructor(clazz);
			if (creatorConstructor != null) {
				creatorConstructor.setAccessible(true);
				beanInfo.setCreatorConstructor(creatorConstructor);

				for (int i = 0; i < creatorConstructor.getParameterTypes().length; ++i) {
					final Annotation[] paramAnnotations = creatorConstructor.getParameterAnnotations()[i];
					JSONField fieldAnnotation = null;
					for (final Annotation paramAnnotation : paramAnnotations) {
						if (paramAnnotation instanceof JSONField) {
							fieldAnnotation = (JSONField) paramAnnotation;
							break;
						}
					}
					if (fieldAnnotation == null) {
						throw new JSONException("illegal json creator");
					}

					final Class<?> fieldClass = creatorConstructor.getParameterTypes()[i];
					final Type fieldType = creatorConstructor.getGenericParameterTypes()[i];
					final Field field = getField(clazz, fieldAnnotation.name());
					if (field != null) {
						field.setAccessible(true);
					}
					final FieldInfo fieldInfo = new FieldInfo(fieldAnnotation.name(), clazz, fieldClass, fieldType, null, field);
					beanInfo.add(fieldInfo);
				}
				return beanInfo;
			}

			final Method factoryMethod = getFactoryMethod(clazz);
			if (factoryMethod != null) {
				factoryMethod.setAccessible(true);
				beanInfo.setFactoryMethod(factoryMethod);

				for (int i = 0; i < factoryMethod.getParameterTypes().length; ++i) {
					final Annotation[] paramAnnotations = factoryMethod.getParameterAnnotations()[i];
					JSONField fieldAnnotation = null;
					for (final Annotation paramAnnotation : paramAnnotations) {
						if (paramAnnotation instanceof JSONField) {
							fieldAnnotation = (JSONField) paramAnnotation;
							break;
						}
					}
					if (fieldAnnotation == null) {
						throw new JSONException("illegal json creator");
					}

					final Class<?> fieldClass = factoryMethod.getParameterTypes()[i];
					final Type fieldType = factoryMethod.getGenericParameterTypes()[i];
					final Field field = getField(clazz, fieldAnnotation.name());
					if (field != null) {
						field.setAccessible(true);
					}
					final FieldInfo fieldInfo = new FieldInfo(fieldAnnotation.name(), clazz, fieldClass, fieldType, null, field);
					beanInfo.add(fieldInfo);
				}
				return beanInfo;
			}

			throw new JSONException("default constructor not found. " + clazz);
		}

		for (final Method method : clazz.getMethods()) {
			final String methodName = method.getName();
			if (methodName.length() < 4) {
				continue;
			}

			if (Modifier.isStatic(method.getModifiers())) {
				continue;
			}

			if (!method.getReturnType().equals(Void.TYPE)) {
				continue;
			}

			if (method.getParameterTypes().length != 1) {
				continue;
			}

			final JSONField annotation = method.getAnnotation(JSONField.class);

			if (annotation != null) {
				if (!annotation.deserialize()) {
					continue;
				}

				if (annotation.name().length() != 0) {
					final String propertyName = annotation.name();
					beanInfo.add(new FieldInfo(propertyName, method, null));
					method.setAccessible(true);
					continue;
				}
			}

			if (methodName.startsWith("set") && Character.isUpperCase(methodName.charAt(3))) {
				String propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);

				final Field field = getField(clazz, propertyName);
				if (field != null) {

					final JSONField fieldAnnotation = field.getAnnotation(JSONField.class);

					if (fieldAnnotation != null && fieldAnnotation.name().length() != 0) {
						propertyName = fieldAnnotation.name();

						beanInfo.add(new FieldInfo(propertyName, method, field));
						continue;
					}
				}

				beanInfo.add(new FieldInfo(propertyName, method, null));
				method.setAccessible(true);
			}
		}

		for (final Field field : clazz.getFields()) {
			if (Modifier.isStatic(field.getModifiers())) {
				continue;
			}

			if (!Modifier.isPublic(field.getModifiers())) {
				continue;
			}

			boolean contains = false;
			for (final FieldInfo item : beanInfo.getFieldList()) {
				if (item.getName().equals(field.getName())) {
					contains = true;
					continue;
				}
			}

			if (contains) {
				continue;
			}

			beanInfo.add(new FieldInfo(field.getName(), null, field));
		}

		return beanInfo;
	}

	public static Field getField(Class<?> clazz, String fieldName) {
		try {
			return clazz.getDeclaredField(fieldName);
		} catch (final Exception e) {
			return null;
		}
	}

	public static Constructor<?> getDefaultConstructor(Class<?> clazz) {
		if (Modifier.isAbstract(clazz.getModifiers())) {
			return null;
		}

		Constructor<?> defaultConstructor = null;
		for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
			if (constructor.getParameterTypes().length == 0) {
				defaultConstructor = constructor;
				break;
			}
		}

		if (defaultConstructor == null) {
			if (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
				for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
					if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0].equals(clazz.getDeclaringClass())) {
						defaultConstructor = constructor;
						break;
					}
				}
			}
		}

		return defaultConstructor;
	}

	public static Constructor<?> getCreatorConstructor(Class<?> clazz) {
		Constructor<?> creatorConstructor = null;

		for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
			final JSONCreator annotation = constructor.getAnnotation(JSONCreator.class);
			if (annotation != null) {
				if (creatorConstructor != null) {
					throw new JSONException("multi-json creator");
				}

				creatorConstructor = constructor;
				break;
			}
		}
		return creatorConstructor;
	}

	public static Method getFactoryMethod(Class<?> clazz) {
		Method factoryMethod = null;

		for (final Method method : clazz.getDeclaredMethods()) {
			if (!Modifier.isStatic(method.getModifiers())) {
				continue;
			}

			if (!clazz.isAssignableFrom(method.getReturnType())) {
				continue;
			}

			final JSONCreator annotation = method.getAnnotation(JSONCreator.class);
			if (annotation != null) {
				if (factoryMethod != null) {
					throw new JSONException("multi-json creator");
				}

				factoryMethod = method;
				break;
			}
		}
		return factoryMethod;
	}

}
